#include "File/BOSSOutputLoader.h"
#include "File/YamlLoader.h"
#include "Functions/File.h"
#include "Functions/PrintTable.h"
#include "Functions/String.h"
#include "Functions/TerminalIO.h"
#include <iomanip>
#include <iostream>
#include <list>
#include <sstream>
#include <string>
#include <utility>

using namespace BOSS_Afterburner::File;
using namespace BOSS_Afterburner::String;
using namespace BOSS_Afterburner::TerminalIO;
using namespace std;

/// @addtogroup Afterburner_scripts
/// @{

/// Print indications for cut flow.
/// @author   Remco de Boer 雷穆克 (r.e.deboer@students.uu.nl or remco.de.boer@ihep.ac.cn)
/// @date     May 21th, 2019
void CutFlow(const char* config_file)
{
  /// -# Load input files.
  YamlLoader       conf(config_file);
  const auto&      filename = conf["Input file or directory"];
  BOSSOutputLoader file(filename.as<string>().c_str());
  if(file.IsZombie()) return;

  /// -# Print embedded cut flow.
  file.PrintCuts(false); /// @todo Check why table values are different the first time you print it.
  if(conf["Print embedded cut flow"].IsDefined() && conf["Print embedded cut flow"].as<bool>())
    file.PrintCuts();

  /// -# Extract specific cuts to print.
  Table       table;
  const auto& cuts = conf["Cuts to print"];
  if(cuts)
  {
    Table cutTable = file.PrintCuts(false);
    for(const auto& row : cutTable.GetRows())
    {
      const auto& label = cuts[row.Name];
      if(!label) continue;

      string name;
      if(label.IsDefined())
        name = label.as<string>();
      else
        name = row.Name;

      table << name << row.LastCell();
      table.SetLastSortValue(row.SortValue);
      table.SetLastName(name);
      table << TableRow::endrow;
    }
  }

  /// -# Compute additional cuts using `TTree::Draw` and `TH1::GetEntries()`.
  const auto& tree  = conf["Tree name"];
  const auto& extra = conf["Extra cuts"];
  if(tree.IsDefined() && extra.IsDefined() && extra.IsSequence())
  {
    auto treeName = tree.as<string>();
    for(const auto& item : extra)
    {
      auto hist = file.Draw(treeName, item);
      if(!hist) continue;

      const auto& label = item["label"];

      string name;
      if(label.IsDefined())
        name = label.as<string>();
      else
        name = hist->GetName();

      table << name << CommaFormattedString(hist->GetEntries());
      table.SetLastSortValue(hist->GetEntries());
      table.SetLastName(name);
      table << TableRow::endrow;
    }
  }
  table.Clean();
  table.Sort();

  /// -# Compute ratios if required by YAML config file.
  const auto& writeperc     = conf["Compute percentages"];
  bool        writepercbool = writeperc.IsDefined() && writeperc.as<bool>();
  if(writepercbool)
  {
    Table newtable;
    Double_t previous, firstval;
    size_t   i = 0;
    for(const auto& row : table.GetRows())
    {
      if(row.GetEntries() < 2) continue;
      newtable << row[0] << row[1];

      TString entry = row[1].c_str();
      entry.ReplaceAll(",", "");
      Double_t val = entry.Atof();
      if(!i)
      {
        firstval = val;
        previous = val;
      }

      Double_t absratio = val / firstval * 100.;
      Double_t relratio = val / previous * 100.;
      previous = val;

      newtable << (string)Form("%.3g \\%%", absratio);
      if(i)
        newtable << (string)Form("%.3g \\%%", relratio);
      else
      {
        firstval = val;
        newtable << (string) "";
      }

      newtable << TableRow::endrow;
      ++i;
    }
    table = newtable;
  }

  /// -# Set table header and alignment and sort based on sort values.
  if(writepercbool)
  {
    table.SetHeader({"Cut", "Number of events", "Absolute", "Relative"});
    table.SetAlign({TableColumn::Left, TableColumn::Right, TableColumn::Right, TableColumn::Right});
  }
  else
  {
    table.SetHeader({"Cut", "Number of events"});
    table.SetAlign({TableColumn::Left, TableColumn::Right});
  }

  /// -# Print and write the resulting table.
  table.Print();
  table.WriteLaTeX("Cutflow");
  table.Write("Cutflow");
}
/// @}

int main(int argc, char* argv[])
{
  CheckMainArguments(argc, argv, 1);
  CutFlow(argv[1]);
  return 0;
}